/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/

#include "include/utils/SkShadowUtils.h"

#include "include/core/SkBlendMode.h"
#include "include/core/SkBlender.h"
#include "include/core/SkBlurTypes.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPoint.h"
#include "include/core/SkPoint3.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkVertices.h"
#include "include/private/SkIDChangeListener.h"
#include "include/private/base/SkTPin.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkRandom.h"
#include "src/core/SkBlurMask.h"
#include "src/core/SkColorFilterPriv.h"
#include "src/core/SkDevice.h"
#include "src/core/SkDrawShadowInfo.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkResourceCache.h"
#include "src/core/SkVerticesPriv.h"



#include <algorithm>
#include <cstring>
#include <functional>
#include <memory>
#include <new>
#include <utility>

using namespace skia_private;

class SkRRect;

///////////////////////////////////////////////////////////////////////////////////////////////////

void SkShadowUtils::ComputeTonalColors(SkColor inAmbientColor, SkColor inSpotColor,
                                       SkColor* outAmbientColor, SkColor* outSpotColor) {
    // For tonal color we only compute color values for the spot shadow.
    // The ambient shadow is greyscale only.

    // Ambient
    *outAmbientColor = SkColorSetARGB(SkColorGetA(inAmbientColor), 0, 0, 0);

    // Spot
    int spotR = SkColorGetR(inSpotColor);
    int spotG = SkColorGetG(inSpotColor);
    int spotB = SkColorGetB(inSpotColor);
    int max = std::max(std::max(spotR, spotG), spotB);
    int min = std::min(std::min(spotR, spotG), spotB);
    SkScalar luminance = 0.5f*(max + min)/255.f;
    SkScalar origA = SkColorGetA(inSpotColor)/255.f;

    // We compute a color alpha value based on the luminance of the color, scaled by an
    // adjusted alpha value. We want the following properties to match the UX examples
    // (assuming a = 0.25) and to ensure that we have reasonable results when the color
    // is black and/or the alpha is 0:
    //     f(0, a) = 0
    //     f(luminance, 0) = 0
    //     f(1, 0.25) = .5
    //     f(0.5, 0.25) = .4
    //     f(1, 1) = 1
    // The following functions match this as closely as possible.
    SkScalar alphaAdjust = (2.6f + (-2.66667f + 1.06667f*origA)*origA)*origA;
    SkScalar colorAlpha = (3.544762f + (-4.891428f + 2.3466f*luminance)*luminance)*luminance;
    colorAlpha = SkTPin(alphaAdjust*colorAlpha, 0.0f, 1.0f);

    // Similarly, we set the greyscale alpha based on luminance and alpha so that
    //     f(0, a) = a
    //     f(luminance, 0) = 0
    //     f(1, 0.25) = 0.15
    SkScalar greyscaleAlpha = SkTPin(origA*(1 - 0.4f*luminance), 0.0f, 1.0f);

    // The final color we want to emulate is generated by rendering a color shadow (C_rgb) using an
    // alpha computed from the color's luminance (C_a), and then a black shadow with alpha (S_a)
    // which is an adjusted value of 'a'.  Assuming SrcOver, a background color of B_rgb, and
    // ignoring edge falloff, this becomes
    //
    //      (C_a - S_a*C_a)*C_rgb + (1 - (S_a + C_a - S_a*C_a))*B_rgb
    //
    // Assuming premultiplied alpha, this means we scale the color by (C_a - S_a*C_a) and
    // set the alpha to (S_a + C_a - S_a*C_a).
    SkScalar colorScale = colorAlpha*(SK_Scalar1 - greyscaleAlpha);
    SkScalar tonalAlpha = colorScale + greyscaleAlpha;
    SkScalar unPremulScale = colorScale / tonalAlpha;
    *outSpotColor = SkColorSetARGB(tonalAlpha*255.999f,
                                   unPremulScale*spotR,
                                   unPremulScale*spotG,
                                   unPremulScale*spotB);
}

static bool fill_shadow_rec(const SkPath& path, const SkPoint3& zPlaneParams,
                            const SkPoint3& lightPos, SkScalar lightRadius,
                            SkColor ambientColor, SkColor spotColor,
                            uint32_t flags, const SkMatrix& ctm, SkDrawShadowRec* rec) {
    SkPoint pt = { lightPos.fX, lightPos.fY };
    if (!SkToBool(flags & kDirectionalLight_ShadowFlag)) {
        // If light position is in device space, need to transform to local space
        // before applying to SkCanvas.
        SkMatrix inverse;
        if (!ctm.invert(&inverse)) {
            return false;
        }
        inverse.mapPoints(&pt, 1);
    }

    rec->fZPlaneParams   = zPlaneParams;
    rec->fLightPos       = { pt.fX, pt.fY, lightPos.fZ };
    rec->fLightRadius    = lightRadius;
    rec->fAmbientColor   = ambientColor;
    rec->fSpotColor      = spotColor;
    rec->fFlags          = flags;

    return true;
}



// Draw an offset spot shadow and outlining ambient shadow for the given path.
void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, const SkPoint3& zPlaneParams,
                               const SkPoint3& lightPos, SkScalar lightRadius,
                               SkColor ambientColor, SkColor spotColor,
                               uint32_t flags) {
    SkDrawShadowRec rec;
    if (!fill_shadow_rec(path, zPlaneParams, lightPos, lightRadius, ambientColor, spotColor,
                         flags, canvas->getTotalMatrix(), &rec)) {
        return;
    }

    // WARNING: OH_Drawing_CanvasDrawShadow implementation fixes light position (0,0) 
    // at screen's top-left corner, making it impossible to use local SkPicture coordinates
    // for shadow lighting. 
    // As this violates Skia's coordinate system contract, we fall back
    // to manual drawPath-based shadow rendering.
    // canvas->private_draw_shadow_rec(path, rec);
    DrawShadowWithSimpleOp(canvas, path, rec);
}


void SkShadowUtils::DrawShadowWithSimpleOp(SkCanvas* canvas, const SkPath& path, const SkDrawShadowRec& rec) {
    if (!canvas) {
        return;
    }

    SkMatrix viewMatrix = canvas->getTotalMatrix();
    canvas->save();
    canvas->resetMatrix();

    bool directional = SkToBool(rec.fFlags & SkShadowFlags::kDirectionalLight_ShadowFlag);

    SkPoint3 zPlaneParams = rec.fZPlaneParams;
    SkPoint3 devLightPos = rec.fLightPos;
    if (!directional) {
        viewMatrix.mapPoints((SkPoint*)&devLightPos.fX, 1);
    }
    float lightRadius = rec.fLightRadius;

    if (SkColorGetA(rec.fAmbientColor) > 0) {
        // Pretransform the path to avoid transforming the stroke, below.
        SkPath devSpacePath;
        path.transform(viewMatrix, &devSpacePath);
        devSpacePath.setIsVolatile(true);

        // The tesselator outsets by AmbientBlurRadius (or 'r') to get the outer ring of
        // the tesselation, and sets the alpha on the path to 1/AmbientRecipAlpha (or 'a').
        //
        // We want to emulate this with a blur. The full blur width (2*blurRadius or 'f')
        // can be calculated by interpolating:
        //
        //            original edge        outer edge
        //         |       |<---------- r ------>|
        //         |<------|--- f -------------->|
        //         |       |                     |
        //    alpha = 1  alpha = a          alpha = 0
        //
        // Taking ratios, f/1 = r/a, so f = r/a and blurRadius = f/2.
        //
        // We now need to outset the path to place the new edge in the center of the
        // blur region:
        //
        //             original   new
        //         |       |<------|--- r ------>|
        //         |<------|--- f -|------------>|
        //         |       |<- o ->|<--- f/2 --->|
        //
        //     r = o + f/2, so o = r - f/2
        //
        // We outset by using the stroker, so the strokeWidth is o/2.
        //
        SkScalar devSpaceOutset = SkDrawShadowMetrics::AmbientBlurRadius(zPlaneParams.fZ);
        SkScalar oneOverA = SkDrawShadowMetrics::AmbientRecipAlpha(zPlaneParams.fZ);
        SkScalar blurRadius = 0.5f*devSpaceOutset*oneOverA;
        SkScalar strokeWidth = 0.5f*(devSpaceOutset - blurRadius);

        // Now draw with blur
        SkPaint paint;
        paint.setColor(rec.fAmbientColor);
        paint.setStrokeWidth(strokeWidth);
        paint.setStyle(SkPaint::kStrokeAndFill_Style);
        SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(blurRadius);
        bool respectCTM = false;
        paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma, respectCTM));
        canvas->drawPath(devSpacePath, paint);
    }

    if (SkColorGetA(rec.fSpotColor) > 0) {
        SkMatrix shadowMatrix;
        SkScalar radius;
        if (!SkDrawShadowMetrics::GetSpotShadowTransform(devLightPos, lightRadius,
                                                         viewMatrix, zPlaneParams,
                                                         path.getBounds(), directional,
                                                         &shadowMatrix, &radius)) {
            return;
        }

        canvas->save();
        canvas->setMatrix(shadowMatrix);

        SkPaint paint;
        paint.setColor(rec.fSpotColor);
        SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
        bool respectCTM = false;
        paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma, respectCTM));
        canvas->drawPath(path, paint);
        canvas->restore();
    }

    canvas->restore();
}

